(function() {
    "use strict";

    var crudAPISpecTests = function crudAPISpecTests() {
        "use strict";

        // Get the colllection
        var coll = db.crud_tests;

        // Setup
        function createTestExecutor(coll, method, verifyResult) {
            return function(args) {
                // Drop collection
                coll.drop();
                // Insert test data
                var r = coll.insertMany(args.insert);
                assert.eq(args.insert.length, r.insertedIds.length);

                // Execute the method with arguments
                r = coll[method].apply(coll, args.params);
                verifyResult(args.result, r);

                // Get all the results
                var results = coll.find({}).sort({_id: 1}).toArray();

                assert.docEq(args.expected, results);
            };
        }

        function checkResultObject(first, second) {
            // Only assert on the "modifiedCount" property when write commands are enabled
            if (db.getMongo().writeMode() === 'commands') {
                assert.docEq(first, second);
            } else {
                var overrideModifiedCount = {
                    modifiedCount: undefined
                };
                assert.docEq(Object.merge(first, overrideModifiedCount),
                             Object.merge(second, overrideModifiedCount));
            }
        }

        // Setup executors
        var deleteManyExecutor = createTestExecutor(coll, 'deleteMany', checkResultObject);
        var deleteOneExecutor = createTestExecutor(coll, 'deleteOne', checkResultObject);
        var bulkWriteExecutor = createTestExecutor(coll, 'bulkWrite', checkResultObject);
        var findOneAndDeleteExecutor =
            createTestExecutor(coll, 'findOneAndDelete', checkResultObject);
        var findOneAndReplaceExecutor =
            createTestExecutor(coll, 'findOneAndReplace', checkResultObject);
        var findOneAndUpdateExecutor =
            createTestExecutor(coll, 'findOneAndUpdate', checkResultObject);
        var insertManyExecutor = createTestExecutor(coll, 'insertMany', checkResultObject);
        var insertOneExecutor = createTestExecutor(coll, 'insertOne', checkResultObject);
        var replaceOneExecutor = createTestExecutor(coll, 'replaceOne', checkResultObject);
        var updateManyExecutor = createTestExecutor(coll, 'updateMany', checkResultObject);
        var updateOneExecutor = createTestExecutor(coll, 'updateOne', checkResultObject);
        var countExecutor = createTestExecutor(coll, 'count', assert.eq);
        var distinctExecutor = createTestExecutor(coll, 'distinct', assert.eq);

        //
        // BulkWrite
        //

        bulkWriteExecutor({
            insert: [{_id: 1, c: 1}, {_id: 2, c: 2}, {_id: 3, c: 3}],
            params: [[
                {insertOne: {document: {_id: 4, a: 1}}},
                {updateOne: {filter: {_id: 5, a: 2}, update: {$set: {a: 2}}, upsert: true}},
                {updateMany: {filter: {_id: 6, a: 3}, update: {$set: {a: 3}}, upsert: true}},
                {deleteOne: {filter: {c: 1}}},
                {insertOne: {document: {_id: 7, c: 2}}},
                {deleteMany: {filter: {c: 2}}},
                {replaceOne: {filter: {c: 3}, replacement: {c: 4}, upsert: true}}
            ]],
            result: {
                acknowledged: true,
                insertedCount: 2,
                matchedCount: 1,
                deletedCount: 3,
                upsertedCount: 2,
                insertedIds: {'0': 4, '4': 7},
                upsertedIds: {'1': 5, '2': 6}
            },
            expected:
                [{"_id": 3, "c": 4}, {"_id": 4, "a": 1}, {"_id": 5, "a": 2}, {"_id": 6, "a": 3}]
        });

        bulkWriteExecutor({
            insert: [{_id: 1, c: 1}, {_id: 2, c: 2}, {_id: 3, c: 3}],
            params: [
                [
                  {insertOne: {document: {_id: 4, a: 1}}},
                  {updateOne: {filter: {_id: 5, a: 2}, update: {$set: {a: 2}}, upsert: true}},
                  {updateMany: {filter: {_id: 6, a: 3}, update: {$set: {a: 3}}, upsert: true}},
                  {deleteOne: {filter: {c: 1}}},
                  {deleteMany: {filter: {c: 2}}},
                  {replaceOne: {filter: {c: 3}, replacement: {c: 4}, upsert: true}}
                ],
                {ordered: false}
            ],
            result: {
                acknowledged: true,
                insertedCount: 1,
                matchedCount: 1,
                deletedCount: 2,
                upsertedCount: 2,
                insertedIds: {'0': 4},
                upsertedIds: {'1': 5, '2': 6}
            },
            expected:
                [{"_id": 3, "c": 4}, {"_id": 4, "a": 1}, {"_id": 5, "a": 2}, {"_id": 6, "a": 3}]
        });

        // DeleteMany
        //

        // DeleteMany when many documents match
        deleteManyExecutor({
            insert: [{_id: 1, x: 11}, {_id: 2, x: 22}, {_id: 3, x: 33}],
            params: [{_id: {$gt: 1}}],
            result: {acknowledged: true, deletedCount: 2},
            expected: [{_id: 1, x: 11}]
        });
        // DeleteMany when no document matches
        deleteManyExecutor({
            insert: [{_id: 1, x: 11}, {_id: 2, x: 22}, {_id: 3, x: 33}],
            params: [{_id: 4}],
            result: {acknowledged: true, deletedCount: 0},
            expected: [{_id: 1, x: 11}, {_id: 2, x: 22}, {_id: 3, x: 33}]
        });
        // DeleteMany when many documents match, no write concern
        deleteManyExecutor({
            insert: [{_id: 1, x: 11}, {_id: 2, x: 22}, {_id: 3, x: 33}],
            params: [{_id: {$gt: 1}}, {w: 0}],
            result: {acknowledged: false},
            expected: [{_id: 1, x: 11}]
        });

        //
        // DeleteOne
        //

        // DeleteOne when many documents match
        deleteOneExecutor({
            insert: [{_id: 1, x: 11}, {_id: 2, x: 22}, {_id: 3, x: 33}],
            params: [{_id: {$gt: 1}}],
            result: {acknowledged: true, deletedCount: 1},
            expected: [{_id: 1, x: 11}, {_id: 3, x: 33}]
        });
        // DeleteOne when one document matches
        deleteOneExecutor({
            insert: [{_id: 1, x: 11}, {_id: 2, x: 22}, {_id: 3, x: 33}],
            params: [{_id: 2}],
            result: {acknowledged: true, deletedCount: 1},
            expected: [{_id: 1, x: 11}, {_id: 3, x: 33}]
        });
        // DeleteOne when no documents match
        deleteOneExecutor({
            insert: [{_id: 1, x: 11}, {_id: 2, x: 22}, {_id: 3, x: 33}],
            params: [{_id: 4}],
            result: {acknowledged: true, deletedCount: 0},
            expected: [{_id: 1, x: 11}, {_id: 2, x: 22}, {_id: 3, x: 33}]
        });
        // DeleteOne when many documents match, no write concern
        deleteOneExecutor({
            insert: [{_id: 1, x: 11}, {_id: 2, x: 22}, {_id: 3, x: 33}],
            params: [{_id: {$gt: 1}}, {w: 0}],
            result: {acknowledged: false},
            expected: [{_id: 1, x: 11}, {_id: 3, x: 33}]
        });

        //
        // FindOneAndDelete
        //

        // FindOneAndDelete when one document matches
        findOneAndDeleteExecutor({
            insert: [{_id: 1, x: 11}, {_id: 2, x: 22}, {_id: 3, x: 33}],
            params: [{_id: {$gt: 2}}, {projection: {x: 1, _id: 0}, sort: {x: 1}}],
            result: {x: 33},
            expected: [{_id: 1, x: 11}, {_id: 2, x: 22}]
        });
        // FindOneAndDelete when one document matches
        findOneAndDeleteExecutor({
            insert: [{_id: 1, x: 11}, {_id: 2, x: 22}, {_id: 3, x: 33}],
            params: [{_id: 2}, {projection: {x: 1, _id: 0}, sort: {x: 1}}],
            result: {x: 22},
            expected: [{_id: 1, x: 11}, {_id: 3, x: 33}]
        });
        // FindOneAndDelete when no documents match
        findOneAndDeleteExecutor({
            insert: [{_id: 1, x: 11}, {_id: 2, x: 22}, {_id: 3, x: 33}],
            params: [{_id: 4}, {projection: {x: 1, _id: 0}, sort: {x: 1}}],
            result: null,
            expected: [{_id: 1, x: 11}, {_id: 2, x: 22}, {_id: 3, x: 33}]
        });

        //
        // FindOneAndReplace
        //

        // FindOneAndReplace when many documents match returning the document before modification
        findOneAndReplaceExecutor({
            insert: [{_id: 1, x: 11}, {_id: 2, x: 22}, {_id: 3, x: 33}],
            params: [{_id: {$gt: 1}}, {x: 32}, {projection: {x: 1, _id: 0}, sort: {x: 1}}],
            result: {x: 22},
            expected: [{_id: 1, x: 11}, {_id: 2, x: 32}, {_id: 3, x: 33}]
        });
        // FindOneAndReplace when many documents match returning the document after modification
        findOneAndReplaceExecutor({
            insert: [{_id: 1, x: 11}, {_id: 2, x: 22}, {_id: 3, x: 33}],
            params: [
                {_id: {$gt: 1}},
                {x: 32},
                {projection: {x: 1, _id: 0}, sort: {x: 1}, returnNewDocument: true}
            ],
            result: {x: 32},
            expected: [{_id: 1, x: 11}, {_id: 2, x: 32}, {_id: 3, x: 33}]
        });
        // FindOneAndReplace when one document matches returning the document before modification
        findOneAndReplaceExecutor({
            insert: [{_id: 1, x: 11}, {_id: 2, x: 22}, {_id: 3, x: 33}],
            params: [{_id: 2}, {x: 32}, {projection: {x: 1, _id: 0}, sort: {x: 1}}],
            result: {x: 22},
            expected: [{_id: 1, x: 11}, {_id: 2, x: 32}, {_id: 3, x: 33}]
        });
        // FindOneAndReplace when one document matches returning the document after modification
        findOneAndReplaceExecutor({
            insert: [{_id: 1, x: 11}, {_id: 2, x: 22}, {_id: 3, x: 33}],
            params: [
                {_id: 2},
                {x: 32},
                {projection: {x: 1, _id: 0}, sort: {x: 1}, returnNewDocument: true}
            ],
            result: {x: 32},
            expected: [{_id: 1, x: 11}, {_id: 2, x: 32}, {_id: 3, x: 33}]
        });
        // FindOneAndReplace when no documents match returning the document before modification
        findOneAndReplaceExecutor({
            insert: [{_id: 1, x: 11}, {_id: 2, x: 22}, {_id: 3, x: 33}],
            params: [{_id: 4}, {x: 44}, {projection: {x: 1, _id: 0}, sort: {x: 1}}],
            result: null,
            expected: [{_id: 1, x: 11}, {_id: 2, x: 22}, {_id: 3, x: 33}]
        });
        // FindOneAndReplace when no documents match with upsert returning the document before
        // modification
        findOneAndReplaceExecutor({
            insert: [{_id: 1, x: 11}, {_id: 2, x: 22}, {_id: 3, x: 33}],
            params: [{_id: 4}, {x: 44}, {projection: {x: 1, _id: 0}, sort: {x: 1}, upsert: true}],
            result: null,
            expected: [{_id: 1, x: 11}, {_id: 2, x: 22}, {_id: 3, x: 33}, {_id: 4, x: 44}]
        });
        // FindOneAndReplace when no documents match returning the document after modification
        findOneAndReplaceExecutor({
            insert: [{_id: 1, x: 11}, {_id: 2, x: 22}, {_id: 3, x: 33}],
            params: [
                {_id: 4},
                {x: 44},
                {projection: {x: 1, _id: 0}, sort: {x: 1}, returnNewDocument: true}
            ],
            result: null,
            expected: [{_id: 1, x: 11}, {_id: 2, x: 22}, {_id: 3, x: 33}]
        });
        // FindOneAndReplace when no documents match with upsert returning the document after
        // modification
        findOneAndReplaceExecutor({
            insert: [{_id: 1, x: 11}, {_id: 2, x: 22}, {_id: 3, x: 33}],
            params: [
                {_id: 4},
                {x: 44},
                {projection: {x: 1, _id: 0}, sort: {x: 1}, returnNewDocument: true, upsert: true}
            ],
            result: {x: 44},
            expected: [{_id: 1, x: 11}, {_id: 2, x: 22}, {_id: 3, x: 33}, {_id: 4, x: 44}]
        });

        assert.throws(function() {
            coll.findOneAndReplace({a: 1}, {$set: {b: 1}});
        });

        //
        // FindOneAndUpdate
        //

        // FindOneAndUpdate when many documents match returning the document before modification
        findOneAndUpdateExecutor({
            insert: [{_id: 1, x: 11}, {_id: 2, x: 22}, {_id: 3, x: 33}],
            params: [{_id: {$gt: 1}}, {$inc: {x: 1}}, {projection: {x: 1, _id: 0}, sort: {x: 1}}],
            result: {x: 22},
            expected: [{_id: 1, x: 11}, {_id: 2, x: 23}, {_id: 3, x: 33}]
        });
        // FindOneAndUpdate when many documents match returning the document after modification
        findOneAndUpdateExecutor({
            insert: [{_id: 1, x: 11}, {_id: 2, x: 22}, {_id: 3, x: 33}],
            params: [
                {_id: {$gt: 1}},
                {$inc: {x: 1}},
                {projection: {x: 1, _id: 0}, sort: {x: 1}, returnNewDocument: true}
            ],
            result: {x: 23},
            expected: [{_id: 1, x: 11}, {_id: 2, x: 23}, {_id: 3, x: 33}]
        });
        // FindOneAndUpdate when one document matches returning the document before modification
        findOneAndUpdateExecutor({
            insert: [{_id: 1, x: 11}, {_id: 2, x: 22}, {_id: 3, x: 33}],
            params: [{_id: 2}, {$inc: {x: 1}}, {projection: {x: 1, _id: 0}, sort: {x: 1}}],
            result: {x: 22},
            expected: [{_id: 1, x: 11}, {_id: 2, x: 23}, {_id: 3, x: 33}]
        });
        // FindOneAndUpdate when one document matches returning the document after modification
        findOneAndUpdateExecutor({
            insert: [{_id: 1, x: 11}, {_id: 2, x: 22}, {_id: 3, x: 33}],
            params: [
                {_id: 2},
                {$inc: {x: 1}},
                {projection: {x: 1, _id: 0}, sort: {x: 1}, returnNewDocument: true}
            ],
            result: {x: 23},
            expected: [{_id: 1, x: 11}, {_id: 2, x: 23}, {_id: 3, x: 33}]
        });
        // FindOneAndUpdate when no documents match returning the document before modification
        findOneAndUpdateExecutor({
            insert: [{_id: 1, x: 11}, {_id: 2, x: 22}, {_id: 3, x: 33}],
            params: [{_id: 4}, {$inc: {x: 1}}, {projection: {x: 1, _id: 0}, sort: {x: 1}}],
            result: null,
            expected: [{_id: 1, x: 11}, {_id: 2, x: 22}, {_id: 3, x: 33}]
        });
        // FindOneAndUpdate when no documents match with upsert returning the document before
        // modification
        findOneAndUpdateExecutor({
            insert: [{_id: 1, x: 11}, {_id: 2, x: 22}, {_id: 3, x: 33}],
            params: [
                {_id: 4},
                {$inc: {x: 1}},
                {projection: {x: 1, _id: 0}, sort: {x: 1}, upsert: true}
            ],
            result: null,
            expected: [{_id: 1, x: 11}, {_id: 2, x: 22}, {_id: 3, x: 33}, {_id: 4, x: 1}]
        });
        // FindOneAndUpdate when no documents match returning the document after modification
        findOneAndUpdateExecutor({
            insert: [{_id: 1, x: 11}, {_id: 2, x: 22}, {_id: 3, x: 33}],
            params: [
                {_id: 4},
                {$inc: {x: 1}},
                {projection: {x: 1, _id: 0}, sort: {x: 1}, returnNewDocument: true}
            ],
            result: null,
            expected: [{_id: 1, x: 11}, {_id: 2, x: 22}, {_id: 3, x: 33}]
        });
        // FindOneAndUpdate when no documents match with upsert returning the document after
        // modification
        findOneAndUpdateExecutor({
            insert: [{_id: 1, x: 11}, {_id: 2, x: 22}, {_id: 3, x: 33}],
            params: [
                {_id: 4},
                {$inc: {x: 1}},
                {projection: {x: 1, _id: 0}, sort: {x: 1}, returnNewDocument: true, upsert: true}
            ],
            result: {x: 1},
            expected: [{_id: 1, x: 11}, {_id: 2, x: 22}, {_id: 3, x: 33}, {_id: 4, x: 1}]
        });

        assert.throws(function() {
            coll.findOneAndUpdate({a: 1}, {});
        });

        assert.throws(function() {
            coll.findOneAndUpdate({a: 1}, {b: 1});
        });

        //
        // InsertMany
        //

        // InsertMany with non-existing documents
        insertManyExecutor({
            insert: [{_id: 1, x: 11}],
            params: [[{_id: 2, x: 22}, {_id: 3, x: 33}]],
            result: {acknowledged: true, insertedIds: [2, 3]},
            expected: [{_id: 1, x: 11}, {_id: 2, x: 22}, {_id: 3, x: 33}]
        });
        // InsertMany with non-existing documents, no write concern
        insertManyExecutor({
            insert: [{_id: 1, x: 11}],
            params: [[{_id: 2, x: 22}, {_id: 3, x: 33}], {w: 0}],
            result: {acknowledged: false},
            expected: [{_id: 1, x: 11}, {_id: 2, x: 22}, {_id: 3, x: 33}]
        });

        //
        // InsertOne
        //

        // InsertOne with non-existing documents
        insertOneExecutor({
            insert: [{_id: 1, x: 11}],
            params: [{_id: 2, x: 22}],
            result: {acknowledged: true, insertedId: 2},
            expected: [{_id: 1, x: 11}, {_id: 2, x: 22}]
        });
        // InsertOne with non-existing documents, no write concern
        insertOneExecutor({
            insert: [{_id: 1, x: 11}],
            params: [{_id: 2, x: 22}, {w: 0}],
            result: {acknowledged: false},
            expected: [{_id: 1, x: 11}, {_id: 2, x: 22}]
        });

        //
        // ReplaceOne
        //

        // ReplaceOne when many documents match
        replaceOneExecutor({
            insert: [{_id: 1, x: 11}, {_id: 2, x: 22}, {_id: 3, x: 33}],
            params: [{_id: {$gt: 1}}, {x: 111}],
            result: {acknowledged: true, matchedCount: 1, modifiedCount: 1},
            expected: [{_id: 1, x: 11}, {_id: 2, x: 111}, {_id: 3, x: 33}]
        });
        // ReplaceOne when one document matches
        replaceOneExecutor({
            insert: [{_id: 1, x: 11}, {_id: 2, x: 22}, {_id: 3, x: 33}],
            params: [{_id: 1}, {_id: 1, x: 111}],
            result: {acknowledged: true, matchedCount: 1, modifiedCount: 1},
            expected: [{_id: 1, x: 111}, {_id: 2, x: 22}, {_id: 3, x: 33}]
        });
        // ReplaceOne when no documents match
        replaceOneExecutor({
            insert: [{_id: 1, x: 11}, {_id: 2, x: 22}, {_id: 3, x: 33}],
            params: [{_id: 4}, {_id: 4, x: 1}],
            result: {acknowledged: true, matchedCount: 0, modifiedCount: 0},
            expected: [{_id: 1, x: 11}, {_id: 2, x: 22}, {_id: 3, x: 33}]
        });
        // ReplaceOne with upsert when no documents match without an id specified
        replaceOneExecutor({
            insert: [{_id: 1, x: 11}, {_id: 2, x: 22}, {_id: 3, x: 33}],
            params: [{_id: 4}, {x: 1}, {upsert: true}],
            result: {acknowledged: true, matchedCount: 0, modifiedCount: 0, upsertedId: 4},
            expected: [{_id: 1, x: 11}, {_id: 2, x: 22}, {_id: 3, x: 33}, {_id: 4, x: 1}]
        });
        // ReplaceOne with upsert when no documents match with an id specified
        replaceOneExecutor({
            insert: [{_id: 1, x: 11}, {_id: 2, x: 22}, {_id: 3, x: 33}],
            params: [{_id: 4}, {_id: 4, x: 1}, {upsert: true}],
            result: {acknowledged: true, matchedCount: 0, modifiedCount: 0, upsertedId: 4},
            expected: [{_id: 1, x: 11}, {_id: 2, x: 22}, {_id: 3, x: 33}, {_id: 4, x: 1}]
        });
        // ReplaceOne with upsert when no documents match with an id specified, no write concern
        replaceOneExecutor({
            insert: [{_id: 1, x: 11}, {_id: 2, x: 22}, {_id: 3, x: 33}],
            params: [{_id: 4}, {_id: 4, x: 1}, {upsert: true, w: 0}],
            result: {acknowledged: false},
            expected: [{_id: 1, x: 11}, {_id: 2, x: 22}, {_id: 3, x: 33}, {_id: 4, x: 1}]
        });
        // ReplaceOne with upsert when no documents match with an id specified, no write concern
        replaceOneExecutor({
            insert: [{_id: 1, x: 11}, {_id: 2, x: 22}, {_id: 3, x: 33}],
            params: [{_id: 4}, {_id: 4, x: 1}, {upsert: true, writeConcern: {w: 0}}],
            result: {acknowledged: false},
            expected: [{_id: 1, x: 11}, {_id: 2, x: 22}, {_id: 3, x: 33}, {_id: 4, x: 1}]
        });

        assert.throws(function() {
            coll.replaceOne({a: 1}, {$set: {b: 1}});
        });

        //
        // UpdateMany
        //

        // UpdateMany when many documents match
        updateManyExecutor({
            insert: [{_id: 1, x: 11}, {_id: 2, x: 22}, {_id: 3, x: 33}],
            params: [{_id: {$gt: 1}}, {$inc: {x: 1}}],
            result: {acknowledged: true, matchedCount: 2, modifiedCount: 2},
            expected: [{_id: 1, x: 11}, {_id: 2, x: 23}, {_id: 3, x: 34}]
        });
        // UpdateMany when one document matches
        updateManyExecutor({
            insert: [{_id: 1, x: 11}, {_id: 2, x: 22}, {_id: 3, x: 33}],
            params: [{_id: 1}, {$inc: {x: 1}}],
            result: {acknowledged: true, matchedCount: 1, modifiedCount: 1},
            expected: [{_id: 1, x: 12}, {_id: 2, x: 22}, {_id: 3, x: 33}]
        });
        // UpdateMany when no documents match
        updateManyExecutor({
            insert: [{_id: 1, x: 11}, {_id: 2, x: 22}, {_id: 3, x: 33}],
            params: [{_id: 4}, {$inc: {x: 1}}],
            result: {acknowledged: true, matchedCount: 0, modifiedCount: 0},
            expected: [{_id: 1, x: 11}, {_id: 2, x: 22}, {_id: 3, x: 33}]
        });
        // UpdateMany with upsert when no documents match
        updateManyExecutor({
            insert: [{_id: 1, x: 11}, {_id: 2, x: 22}, {_id: 3, x: 33}],
            params: [{_id: 4}, {$inc: {x: 1}}, {upsert: true}],
            result: {acknowledged: true, matchedCount: 0, modifiedCount: 0, upsertedId: 4},
            expected: [{_id: 1, x: 11}, {_id: 2, x: 22}, {_id: 3, x: 33}, {_id: 4, x: 1}]
        });
        // UpdateMany with upsert when no documents match, no write concern
        updateManyExecutor({
            insert: [{_id: 1, x: 11}, {_id: 2, x: 22}, {_id: 3, x: 33}],
            params: [{_id: 4}, {$inc: {x: 1}}, {upsert: true, w: 0}],
            result: {acknowledged: false},
            expected: [{_id: 1, x: 11}, {_id: 2, x: 22}, {_id: 3, x: 33}, {_id: 4, x: 1}]
        });

        assert.throws(function() {
            coll.updateMany({a: 1}, {});
        });

        assert.throws(function() {
            coll.updateMany({a: 1}, {b: 1});
        });

        //
        // UpdateOne
        //

        // UpdateOne when many documents match
        updateOneExecutor({
            insert: [{_id: 1, x: 11}, {_id: 2, x: 22}, {_id: 3, x: 33}],
            params: [{_id: {$gt: 1}}, {$inc: {x: 1}}],
            result: {acknowledged: true, matchedCount: 1, modifiedCount: 1},
            expected: [{_id: 1, x: 11}, {_id: 2, x: 23}, {_id: 3, x: 33}]
        });
        // UpdateOne when one document matches
        updateOneExecutor({
            insert: [{_id: 1, x: 11}, {_id: 2, x: 22}, {_id: 3, x: 33}],
            params: [{_id: 1}, {$inc: {x: 1}}],
            result: {acknowledged: true, matchedCount: 1, modifiedCount: 1},
            expected: [{_id: 1, x: 12}, {_id: 2, x: 22}, {_id: 3, x: 33}]
        });
        // UpdateOne when no documents match
        updateOneExecutor({
            insert: [{_id: 1, x: 11}, {_id: 2, x: 22}, {_id: 3, x: 33}],
            params: [{_id: 4}, {$inc: {x: 1}}],
            result: {acknowledged: true, matchedCount: 0, modifiedCount: 0},
            expected: [{_id: 1, x: 11}, {_id: 2, x: 22}, {_id: 3, x: 33}]
        });

        // UpdateOne with upsert when no documents match
        updateOneExecutor({
            insert: [{_id: 1, x: 11}, {_id: 2, x: 22}, {_id: 3, x: 33}],
            params: [{_id: 4}, {$inc: {x: 1}}, {upsert: true}],
            result: {acknowledged: true, matchedCount: 0, modifiedCount: 0, upsertedId: 4},
            expected: [{_id: 1, x: 11}, {_id: 2, x: 22}, {_id: 3, x: 33}, {_id: 4, x: 1}]
        });
        // UpdateOne when many documents match, no write concern
        updateOneExecutor({
            insert: [{_id: 1, x: 11}, {_id: 2, x: 22}, {_id: 3, x: 33}],
            params: [{_id: {$gt: 1}}, {$inc: {x: 1}}, {w: 0}],
            result: {acknowledged: false},
            expected: [{_id: 1, x: 11}, {_id: 2, x: 23}, {_id: 3, x: 33}]
        });

        assert.throws(function() {
            coll.updateOne({a: 1}, {});
        });

        assert.throws(function() {
            coll.updateOne({a: 1}, {b: 1});
        });

        //
        // Count
        //

        // Simple count of all elements
        countExecutor({
            insert: [{_id: 1, x: 11}, {_id: 2, x: 22}, {_id: 3, x: 33}],
            params: [{}],
            result: 3,
            expected: [{_id: 1, x: 11}, {_id: 2, x: 22}, {_id: 3, x: 33}]
        });
        // Simple count no arguments
        countExecutor({
            insert: [{_id: 1, x: 11}, {_id: 2, x: 22}, {_id: 3, x: 33}],
            params: [],
            result: 3,
            expected: [{_id: 1, x: 11}, {_id: 2, x: 22}, {_id: 3, x: 33}]
        });
        // Simple count filtered
        countExecutor({
            insert: [{_id: 1, x: 11}, {_id: 2, x: 22}, {_id: 3, x: 33}],
            params: [{_id: {$gt: 1}}],
            result: 2,
            expected: [{_id: 1, x: 11}, {_id: 2, x: 22}, {_id: 3, x: 33}]
        });
        // Simple count of all elements, applying limit
        countExecutor({
            insert: [{_id: 1, x: 11}, {_id: 2, x: 22}, {_id: 3, x: 33}],
            params: [{}, {limit: 1}],
            result: 1,
            expected: [{_id: 1, x: 11}, {_id: 2, x: 22}, {_id: 3, x: 33}]
        });
        // Simple count of all elements, applying skip
        countExecutor({
            insert: [{_id: 1, x: 11}, {_id: 2, x: 22}, {_id: 3, x: 33}],
            params: [{}, {skip: 1}],
            result: 2,
            expected: [{_id: 1, x: 11}, {_id: 2, x: 22}, {_id: 3, x: 33}]
        });
        // Simple count no arguments, applying hint
        countExecutor({
            insert: [{_id: 1, x: 11}, {_id: 2, x: 22}, {_id: 3, x: 33}],
            params: [{}, {hint: {"_id": 1}}],
            result: 3,
            expected: [{_id: 1, x: 11}, {_id: 2, x: 22}, {_id: 3, x: 33}]
        });

        //
        // Distinct
        //

        // Simple distinct of field x no filter
        distinctExecutor({
            insert: [{_id: 1, x: 11}, {_id: 2, x: 22}, {_id: 3, x: 33}],
            params: ['x'],
            result: [11, 22, 33],
            expected: [{_id: 1, x: 11}, {_id: 2, x: 22}, {_id: 3, x: 33}]
        });
        // Simple distinct of field x
        distinctExecutor({
            insert: [{_id: 1, x: 11}, {_id: 2, x: 22}, {_id: 3, x: 33}],
            params: ['x', {}],
            result: [11, 22, 33],
            expected: [{_id: 1, x: 11}, {_id: 2, x: 22}, {_id: 3, x: 33}]
        });
        // Simple distinct of field x filtered
        distinctExecutor({
            insert: [{_id: 1, x: 11}, {_id: 2, x: 22}, {_id: 3, x: 33}],
            params: ['x', {x: {$gt: 11}}],
            result: [22, 33],
            expected: [{_id: 1, x: 11}, {_id: 2, x: 22}, {_id: 3, x: 33}]
        });
        // Simple distinct of field x filtered with maxTimeMS
        distinctExecutor({
            insert: [{_id: 1, x: 11}, {_id: 2, x: 22}, {_id: 3, x: 33}],
            params: ['x', {x: {$gt: 11}}, {maxTimeMS: 100000}],
            result: [22, 33],
            expected: [{_id: 1, x: 11}, {_id: 2, x: 22}, {_id: 3, x: 33}]
        });

        //
        // Find
        //

        coll.deleteMany({});
        // Insert all of them
        coll.insertMany([{a: 0, b: 0}, {a: 1, b: 1}]);

        // Simple projection
        var result =
            coll.find({}).sort({a: 1}).limit(1).skip(1).projection({_id: 0, a: 1}).toArray();
        assert.docEq(result, [{a: 1}]);

        // Simple tailable cursor
        var cursor = coll.find({}).sort({a: 1}).tailable();
        assert.eq(34, (cursor._options & ~DBQuery.Option.slaveOk));
        var cursor = coll.find({}).sort({a: 1}).tailable(false);
        assert.eq(2, (cursor._options & ~DBQuery.Option.slaveOk));

        // Check modifiers
        var cursor = coll.find({}).modifiers({$hint: 'a_1'});
        assert.eq('a_1', cursor._query['$hint']);

        // allowPartialResults
        var cursor = coll.find({}).allowPartialResults();
        assert.eq(128, (cursor._options & ~DBQuery.Option.slaveOk));

        // noCursorTimeout
        var cursor = coll.find({}).noCursorTimeout();
        assert.eq(16, (cursor._options & ~DBQuery.Option.slaveOk));

        // oplogReplay
        var cursor = coll.find({}).oplogReplay();
        assert.eq(8, (cursor._options & ~DBQuery.Option.slaveOk));

        //
        // Aggregation
        //

        coll.deleteMany({});
        // Insert all of them
        coll.insertMany([{a: 0, b: 0}, {a: 1, b: 1}]);

        // Simple aggregation with useCursor
        var result = coll.aggregate([{$match: {}}], {useCursor: true}).toArray();
        assert.eq(2, result.length);

        // Simple aggregation with batchSize
        var result = coll.aggregate([{$match: {}}], {batchSize: 2}).toArray();
        assert.eq(2, result.length);

        // Drop collection
        coll.drop();
        coll.ensureIndex({a: 1}, {unique: true});

        // Should throw duplicate key error
        assert.throws(function() {
            coll.insertMany([{a: 0, b: 0}, {a: 0, b: 1}]);
        });

        assert(coll.findOne({a: 0, b: 0}) != null);
        assert.throws(function() {
            coll.insertOne({a: 0, b: 0});
        });

        assert.throws(function() {
            coll.updateOne({b: 2}, {$set: {a: 0}}, {upsert: true});
        });

        assert.throws(function() {
            coll.updateMany({b: 2}, {$set: {a: 0}}, {upsert: true});
        });

        assert.throws(function() {
            coll.deleteOne({$invalidFieldName: {a: 1}});
        });

        assert.throws(function() {
            coll.deleteMany({$set: {a: 1}});
        });

        assert.throws(function() {
            coll.bulkWrite([{insertOne: {document: {_id: 4, a: 0}}}]);
        });
    };

    crudAPISpecTests();
})();
